/*
 * (C) Copyright 2013 George Daswani (georgedaswani@gmail.com) and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *    George Daswani (georgedaswani@gmail.com)
 */

package am.api.wrapper;

import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.apache.log4j.Logger;

import am.api.AMConnection;
import am.api.model.AMString;
import am.api.util.NLS;

public class LocalXAResource implements XAResource {

	private final static Logger logger = Logger
			.getLogger(LocalXAResource.class);

	private final AMConnection connection;
	private Xid currentXid;

	public LocalXAResource(AMConnection connection) {
		this.connection = connection;
	}

	@Override
	public void commit(Xid xid, boolean onePhase) throws XAException {

		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Xid: [%s], onePhase = [%s]", xid,
					onePhase));
		}

		if (xid == null)
			throw new NullPointerException("xid is null");

		if (!this.currentXid.equals(xid))
			throw new XAException("Invalid Xid: expected " + this.currentXid
					+ ", but was " + xid);

		try {

			int status = connection.commit();

			if (status != 0) {

				AMString errorMsg = AMString.create(1024);
				connection.lastErrorMsg(errorMsg);
				connection.clearLastError();
				throw new IllegalStateException(String.format(
						NLS.ERRORS.getString("transaction.cannot.commit"),
						status, errorMsg.toString()));

			}

			if (logger.isDebugEnabled()) {
				logger.debug(String.format("connection: [%s]", connection));
			}

		} catch (Exception e) {
			throw (XAException) new XAException().initCause(e);
		} finally {
			this.currentXid = null;
		}
	}

	@Override
	public void end(Xid xid, int flags) throws XAException {

		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Xid: [%s], flags = [%d]", xid, flags));
		}

		if (xid == null)
			throw new NullPointerException("xid is null");

		if (!this.currentXid.equals(xid))
			throw new XAException("Invalid Xid: expected " + this.currentXid
					+ ", but was " + xid);
	}

	@Override
	public void forget(Xid xid) throws XAException {

		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Xid: [%s]", xid));
		}

		if (xid != null && this.currentXid.equals(xid)) {
			this.currentXid = null;
		}
	}

	@Override
	public int getTransactionTimeout() throws XAException {
		return 0;
	}

	public synchronized Xid getXid() {
		return currentXid;
	}

	@Override
	public boolean isSameRM(XAResource xaResource) throws XAException {
		return this == xaResource;
	}

	@Override
	public int prepare(Xid xid) throws XAException {
		return XAResource.XA_OK;
	}

	@Override
	public Xid[] recover(int flag) throws XAException {
		return new Xid[0];
	}

	@Override
	public void rollback(Xid xid) throws XAException {

		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Xid: [%s]", xid));
		}

		if (xid == null)
			throw new NullPointerException("xid is null");

		if (!this.currentXid.equals(xid))
			throw new XAException("Invalid Xid: expected " + this.currentXid
					+ ", but was " + xid);

		try {
			connection.rollBack();
			if (logger.isDebugEnabled()) {
				logger.debug(String.format("rollback - connection: [%s]",
						connection));
			}

		} catch (Exception e) {
			throw (XAException) new XAException().initCause(e);
		} finally {
			this.currentXid = null;
		}
	}

	@Override
	public boolean setTransactionTimeout(int seconds) throws XAException {
		return false;
	}

	public synchronized void start(Xid xid, int flag) throws XAException {

		if (logger.isDebugEnabled()) {
			logger.debug(String.format("Xid: [%s], flag = [%d]", xid, flag));
		}

		if (flag == XAResource.TMNOFLAGS) {

			if (this.currentXid != null) {
				throw new XAException(
						"Already enlisted in another transaction with xid "
								+ xid);
			}

			try {
				connection.startTransaction();
				if (logger.isDebugEnabled()) {
					logger.debug(String.format("start - connection: [%s]",
							connection));
				}
			} catch (Exception e) {
				throw (XAException) new XAException(
						"Count not turn start transaction for a XA transaction")
						.initCause(e);
			} finally {
				this.currentXid = xid;
			}

		} else if (flag == XAResource.TMRESUME) {
			if (xid != this.currentXid) {
				throw new XAException(
						"Attempting to resume in different transaction: expected "
								+ this.currentXid + ", but was " + xid);
			}
		} else {
			throw new XAException("Unknown start flag " + flag);
		}
	}

}
